package com.qd.flow.service.bill;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.cdqidi.constant.ConstantDto;
import com.cdqidi.db.BaseService;
import com.cdqidi.dto.CacheKey;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.RedisTemplateUtil;
import com.qd.common.flow.constant.FlowConstants;
import com.qd.common.flow.domain.bill.FlowBillDTO;
import com.qd.common.flow.domain.bill.FlowProcessTreeDTO;
import com.qd.common.flow.domain.bill.FlowQueryDTO;
import com.qd.common.flow.domain.bill.ProcessTreeDTO;
import com.qd.common.flow.domain.index.ProcessCountDTO;
import com.qd.common.sys.util.SecurityUtils;
import com.qd.flow.mapper.bill.FlowBillMapper;
import com.qd.flow.model.bill.FlowBill;
import com.qd.system.service.org.OrgService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;

import static java.util.stream.Collectors.toList;

/**
 * 审批业务
 *
 * @author sjk
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class FlowBillService extends BaseService<FlowBillMapper, FlowBill> {

    private static final String PREFIX_ID = "flow_bill_billId_{0}";

    private final RedisTemplateUtil<FlowBill> redisTemplateUtil;
    private final FlowTaskService flowTaskService;
    private final OrgService orgService;

    /**
     * 保存
     *
     * @param dto 页面对象
     * @return 页面对象
     */
    @Transactional(rollbackFor = Exception.class)
    public FlowBillDTO save(FlowBillDTO dto) {
        final FlowBill model = dtoToModel(dto);
        try {
            if (super.save(model)) {
                return modelToDto(model);
            }
            throw new ApiException("添加失败");
        } finally {
            clearCache(model);
            model.freeData();
        }
    }

    /**
     * 修改
     *
     * @param dto 页面对象
     * @return 页面对象
     */
    @Transactional(rollbackFor = Exception.class)
    public FlowBillDTO update(FlowBillDTO dto) {
        final FlowBill historyModel = Optional.ofNullable(getById(dto.getBillId())).orElseThrow(() -> new ApiException("ID不存在,不能更新"));
        final FlowBill model = dtoToModel(dto);
        try{
            if (super.updateById(model)) {
                return modelToDto(model);
            }
            throw new ApiException("更新失败");
        }finally {
            clearCache(historyModel);
            historyModel.freeData();
            model.freeData();
        }
    }

    /**
     * 批量添加
     *
     * @param mList 数据库对象
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveBatch(List<FlowBill> mList) {
        try {
            if (!super.saveBatch(mList)) {
                throw new ApiException("批量添加失败");
            }
        } finally {
            clearCaches(mList);
        }
    }

    /**
     * 批量更新
     *
     * @param mList 数据库对象
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateBatchIds(List<FlowBill> mList) {
        final List<String> ids = mList.stream().map(FlowBill::getBillId).collect(toList());
        final List<FlowBill> historyModelList = checkIds(ids);
        try {
            if (!super.updateBatchById(mList)) {
                throw new ApiException("批量更新失败");
            }
        } finally {
            clearCaches(historyModelList);
            historyModelList.clear();
            ids.clear();
        }
    }

    /**
     * 删除，单个
     *
     * @param billId 主键 
     */
    @Transactional(rollbackFor = Exception.class)
    public void removeById(String billId) {
        final FlowBill historyModel = Optional.ofNullable(getById(billId)).orElseThrow(() -> new ApiException("数据不存在,不能删除"));
        try {
            if (!super.removeById(billId)) {
                throw new ApiException("删除失败");
            }
            flowTaskService.removeByBillId(billId);
        } finally {
            clearCache(historyModel);
            historyModel.freeData();
        }
    }

    /**
     * 批量删除
     *
     * @param ids 主键集合
     */
    public void removeBatchIds(List<String> ids) {
       ids.forEach(this::removeById);
    }

    /**
     * 根据传入对象构建查询参数，判断是否存在
     *
     * @param dto 参数
     * @return 存在为true
     */
    public Boolean isExists(FlowBillDTO dto) {
        final QueryWrapper<FlowBill> queryWrapper = createQueryWrapper(dto);
        final boolean exists = super.count(queryWrapper) > 0;
        queryWrapper.clear();
        return exists;
    }

    /**
     * 根据主键判断数据是否存在
     *
     * @param billId 主键 
     * @return 存在为true
     */
    @Transactional(readOnly = true)
    public Boolean isExistsById(String billId) {
        boolean exists = false;
        if (StringUtils.hasLength(billId)) {
            final FlowBill model = getById(billId);
            exists = (null != model);
            if (exists) {
                model.freeData();
            }
        }
        return exists;
    }

    @Transactional(rollbackFor = Exception.class)
    public void removeByBid(String bid) {
        final QueryWrapper<FlowBill> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("bid", bid);
        queryWrapper.eq("bill_state", FlowConstants.BILL_STATE_0);
        List<FlowBill> list = super.list(queryWrapper);
        queryWrapper.clear();
        if (!list.isEmpty()) {
            removeBatchIds(list.stream().map(FlowBill::getBillId).collect(toList()));
        }
    }

    /**
     * 根据主键获取数据
     *
     * @param billId 主键 
     * @return 单条记录
     */
    public FlowBill getById(String billId) {
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, billId);
        return redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY), () -> super.getById(billId), FlowBill.class);
    }

    /**
     * 根据主键获取数据
     *
     * @param billId 主键 
     * @return 单条记录，转换成页面对象
     */
    @Transactional(readOnly = true)
    public FlowBillDTO getByIdDto(String billId) {
        return Optional.ofNullable(getById(billId)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录
     */
    public FlowBill getOne(FlowBillDTO dto) {
        final QueryWrapper<FlowBill> queryWrapper = createQueryWrapper(dto);
        FlowBill model = super.getOne(queryWrapper);
        queryWrapper.clear();
        return model;
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录，转换成页面对象
     */
    public FlowBillDTO getOneDto(FlowBillDTO dto) {
        return Optional.ofNullable(getOne(dto)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据
     */
    public List<FlowBill> getList(FlowBillDTO dto) {
        final QueryWrapper<FlowBill> queryWrapper = createQueryWrapper(dto);
        final List<FlowBill> list = super.list(queryWrapper);
        queryWrapper.clear();
        return list;
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据，转换成页面对象
     */
    @Transactional(readOnly = true)
    public List<FlowBillDTO> getListDto(FlowBillDTO dto) {
        return modelToDtoList(getList(dto));
    }

    /**
     * 分页数据
     *
     * @param page 分页对象
     * @param dto  查询参数
     * @return 分页信息
     */
    @Transactional(readOnly = true)
    public IPage<FlowBillDTO> page(Page<FlowBill> page, FlowBillDTO dto) {
        final QueryWrapper<FlowBill> queryWrapper = createQueryWrapper(dto, "t1.");
        final IPage<FlowBillDTO> iPage = baseMapper.page(page, queryWrapper);
        Optional.ofNullable(iPage.getRecords()).ifPresent(l-> l.forEach(this::buildDisplay));
        queryWrapper.clear();
        return iPage;
    }

    /**
     * 待办任务列表
     *
     * @param page 分页对象
     * @param dto  查询参数
     * @return 分页信息
     */
    @Transactional(readOnly = true)
    public IPage<FlowBillDTO> toDoTaskPage(Page<FlowBill> page, FlowQueryDTO dto) {
        final QueryWrapper<FlowQueryDTO> queryWrapper = createQueryWrapper(dto);
        if (StringUtils.hasLength(dto.getBeginTime())) {
            queryWrapper.apply("date_format(t2.create_time,'%Y-%m-%d') >= {0}", dto.getBeginTime());
        }
        if (StringUtils.hasLength(dto.getEndTime())) {
            queryWrapper.apply("date_format(t2.create_time,'%Y-%m-%d') <= {0}", dto.getEndTime());
        }
        queryWrapper.orderByAsc("t1.create_time");
        final IPage<FlowBillDTO> iPage = baseMapper.toDoTaskPage(page, queryWrapper);
        Optional.ofNullable(iPage.getRecords()).ifPresent(l -> l.forEach(this::buildDisplay));
        queryWrapper.clear();
        return iPage;
    }

    /**
     * 分页数据
     *
     * @param page 分页对象
     * @param dto  查询参数
     * @return 分页信息
     */
    @Transactional(readOnly = true)
    public IPage<FlowBillDTO> completedTaskPage(Page<FlowBill> page, FlowQueryDTO dto) {
        final QueryWrapper<FlowQueryDTO> queryWrapper = createQueryWrapper(dto);
        if (StringUtils.hasLength(dto.getBeginTime())) {
            queryWrapper.apply("date_format(t2.add_time,'%Y-%m-%d') >= {0}", dto.getBeginTime());
        }
        if (StringUtils.hasLength(dto.getEndTime())) {
            queryWrapper.apply("date_format(t2.add_time,'%Y-%m-%d') <= {0}", dto.getEndTime());
        }
        queryWrapper.orderByAsc("t1.create_time");
        final IPage<FlowBillDTO> iPage = baseMapper.completedTaskPage(page, queryWrapper);
        Optional.ofNullable(iPage.getRecords()).ifPresent(l -> l.forEach(this::buildDisplay));
        queryWrapper.clear();
        return iPage;
    }



    /**
     * 清空缓存
     *
     * @param model 数据库对象
     */
    public void clearCache(FlowBill model) {
        if (null == model) {
            return;
        }
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, model.getBillId()));
    }

    /**
     * 批量清空缓存
     *
     * @param list 数据库对象
     */
    public void clearCaches(List<FlowBill> list) {
        if (null == list || list.isEmpty()) {
            return;
        }
        List<String> ids = list.stream().map(model -> RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, model.getBillId())).collect(toList());
        if (!ids.isEmpty()) {
            redisTemplateUtil.deleteByKey(ids);
            ids.clear();
        }
    }

    /**
     * 数据库对象转页面对象
     *
     * @param model 数据库对象
     * @return 页面对象
     */
    public FlowBillDTO modelToDto(FlowBill model) {
        if (null == model) {
            return null;
        }
        final FlowBillDTO dto = new FlowBillDTO();
        dto.setBillId(model.getBillId());
        dto.setProcessCode(model.getProcessCode());
        dto.setProcessName(model.getProcessName());
        dto.setBid(model.getBid());
        dto.setCreateTime(model.getCreateTime());
        dto.setFinishTime(model.getFinishTime());
        dto.setTaskName(model.getTaskName());
        dto.setBillState(model.getBillState());
        dto.setProcessUserName(model.getProcessUserName());
        dto.setProcessOrgName(model.getProcessOrgName());
        dto.setProcessId(model.getProcessId());
        dto.setOperatorType(model.getOperatorType());
        dto.setProcessUserId(model.getProcessUserId());
        dto.setProcessOrg(model.getProcessOrg());
        buildDisplay(dto);
        return dto;
    }

    /**
     * 页面对象转数据库对象
     *
     * @param dto 页面对象
     * @return 数据库对象
     */
    public FlowBill dtoToModel(FlowBillDTO dto) {
        if (null == dto) {
            return null;
        }
        final FlowBill model = new FlowBill();
        model.setBillId(dto.getBillId());
        model.setProcessCode(dto.getProcessCode());
        model.setProcessName(dto.getProcessName());
        model.setBid(dto.getBid());
        model.setCreateTime(dto.getCreateTime());
        model.setFinishTime(dto.getFinishTime());
        model.setTaskName(dto.getTaskName());
        model.setBillState(dto.getBillState());
        model.setProcessUserName(dto.getProcessUserName());
        model.setProcessOrgName(dto.getProcessOrgName());
        model.setProcessId(dto.getProcessId());
        model.setOperatorType(dto.getOperatorType());
        model.setProcessUserId(dto.getProcessUserId());
        model.setProcessOrg(dto.getProcessOrg());
        return model;
    }

    /**
     * 码表转换
     *
     * @param dto 页面对象
     */
    public void buildDisplay(FlowBillDTO dto) {
    }

    /**
     * 数据库对象转页面对象
     *
     * @param list 数据库对象
     * @return 页面对象
     */
    public List<FlowBillDTO> modelToDtoList(List<FlowBill> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::modelToDto).collect(toList());
    }

    /**
     * 页面对象转数据库对象
     *
     * @param list 页面对象
     * @return 数据库对象
     */
    public List<FlowBill> dtoToModelList(List<FlowBillDTO> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::dtoToModel).collect(toList());
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto 查询参数，不为空的属性会被自动加入
     */
    public QueryWrapper<FlowBill> createQueryWrapper(FlowBillDTO dto) {
        return createQueryWrapper(dto, "");
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto        查询参数，不为空的属性会被自动加入
     * @param tableAlias 表别名
     */
    public QueryWrapper<FlowBill> createQueryWrapper(FlowBillDTO dto, String tableAlias) {
        final QueryWrapper<FlowBill> queryWrapper = new QueryWrapper<>();
        if (null != dto) {
            if (StringUtils.hasLength(tableAlias) && tableAlias.charAt(tableAlias.length() - 1) != '.') {
                tableAlias += ".";
            }
            if (StringUtils.hasLength(dto.getProcessCode())) {
                queryWrapper.eq(tableAlias + "process_code", dto.getProcessCode());
            }
            if (StringUtils.hasLength(dto.getProcessName())) {
                queryWrapper.eq(tableAlias + "process_name", dto.getProcessName());
            }
            if (StringUtils.hasLength(dto.getBid())) {
                queryWrapper.eq(tableAlias + "bid", dto.getBid());
            }
            if (StringUtils.hasLength(dto.getTaskName())) {
                queryWrapper.eq(tableAlias + "task_name", dto.getTaskName());
            }
            if (StringUtils.hasLength(dto.getProcessId())) {
                queryWrapper.eq(tableAlias + "process_id", dto.getProcessId());
            }
            if (StringUtils.hasLength(dto.getOperatorType())) {
                queryWrapper.eq(tableAlias + "operator_type", dto.getOperatorType());
            }
        }
        return queryWrapper;
    }

    private QueryWrapper<FlowQueryDTO> createQueryWrapper(FlowQueryDTO dto) {
        final QueryWrapper<FlowQueryDTO> queryWrapper = new QueryWrapper<>();
        if (null != dto) {
            if (StringUtils.hasLength(dto.getProcessCode())) {
                queryWrapper.eq("t1.process_code", dto.getProcessCode());
            }
            if (StringUtils.hasLength(dto.getProcessName())) {
                queryWrapper.like("t1.process_name", dto.getProcessName());
            }
            if (StringUtils.hasLength(dto.getTaskName())) {
                queryWrapper.like("t1.task_name", dto.getTaskName());
            }
            if (null != dto.getBillState()) {
                queryWrapper.eq("t1.bill_state", dto.getBillState());
            }
            if (StringUtils.hasLength(dto.getApproval())) {
                queryWrapper.eq("t2.approval", dto.getApproval());
            }
            if (StringUtils.hasLength(dto.getApprovalDepart())) {
                queryWrapper.eq("t2.approval_depart", dto.getApprovalDepart());
            }
        }
        return queryWrapper;
    }
    /**
     * 批量检查主键在数据库中是否存在
     *
     * @param ids 主键集合
     * @return 主键查询出来的集合
     */
    public List<FlowBill> checkIds(List<String> ids) {
        if (null == ids || ids.isEmpty()) {
            throw new ApiException("ids不能为空");
        }
        final List<FlowBill> list = super.listByIds(ids);
        if (list.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(ids.toArray()));
        }
        if (ids.size() == list.size()) {
            return list;
        }
        List<String> dbIds = list.stream().map(FlowBill::getBillId).collect(toList());
        final List<String> notExistsList = ids.stream().filter(id -> !dbIds.contains(id)).collect(toList());
        dbIds.clear();
        if (!notExistsList.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(notExistsList.toArray()));
        }
        return list;
    }

    @Transactional(readOnly = true)
    public FlowBillDTO getRunning(String bid, String processId,String operatorType) {
        FlowBillDTO query = new FlowBillDTO();
        query.setBid(bid);
        query.setProcessId(processId);
        query.setBillState(FlowConstants.BILL_STATE_0);
        query.setOperatorType(operatorType);
        FlowBillDTO flowBillDTO = getOneDto(query);
        query.freeData();
        return flowBillDTO;
    }

    @Transactional(readOnly = true)
    public Boolean running(String processId) {
        FlowBillDTO query = new FlowBillDTO();
        query.setProcessId(processId);
        query.setBillState(FlowConstants.BILL_STATE_0);
        Boolean exists = isExists(query);
        query.freeData();
        return exists;
    }

    @Transactional(readOnly = true)
    public List<ProcessCountDTO> countByProcessId(String orgId) {
        QueryWrapper<FlowBill> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("t1.bill_state", FlowConstants.BILL_STATE_0);
        if (StringUtils.hasLength(orgId)) {
            queryWrapper.eq("t2.approval_depart", orgId);
        }
        queryWrapper.groupBy("t1.process_id");
        List<com.qd.common.flow.domain.index.ProcessCountDTO> list = getBaseMapper().countByProcessId(queryWrapper);
        queryWrapper.clear();
        return list;
    }

    @Transactional(readOnly = true)
    public Long hisTaskCount(String orgId, String personId) {
        QueryWrapper<FlowBill> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("t1.bill_state", FlowConstants.BILL_STATE_1);
        if (StringUtils.hasLength(orgId)) {
            queryWrapper.eq("t2.approval_depart", orgId);
        }
        if (StringUtils.hasLength(personId)) {
            queryWrapper.eq("t2.approval", personId);
        }
        Long count = getBaseMapper().hisTaskCount(queryWrapper);
        queryWrapper.clear();
        return SqlHelper.retCount(count);
    }

    @Transactional(readOnly = true)
    public FlowProcessTreeDTO processTree(String billId) {
        FlowBill bill = Optional.ofNullable(getById(billId)).orElseThrow(() -> new ApiException("审批业务ID值错误"));
        FlowProcessTreeDTO flowProcessTreeDTO = new FlowProcessTreeDTO();
        buildFlowCreate(bill, flowProcessTreeDTO);
        List<ProcessTreeDTO> list = getBaseMapper().processTree(billId,bill.getProcessId());
        if (!list.isEmpty()) {
            List<FlowProcessTreeDTO.ApprovalDTO> approvalList = new ArrayList<>(list.size());
            for (ProcessTreeDTO processTreeDTO : list) {
                buildApprovalDTO(approvalList, processTreeDTO);
            }
            flowProcessTreeDTO.setApprovalList(approvalList);
        }
        return flowProcessTreeDTO;
    }


    private void buildApprovalDTO(List<FlowProcessTreeDTO.ApprovalDTO> approvalList, ProcessTreeDTO processTreeDTO) {
        FlowProcessTreeDTO.ApprovalDTO approvalDTO = new FlowProcessTreeDTO.ApprovalDTO();
        approvalDTO.setProcessId(processTreeDTO.getProcessId());
        approvalDTO.setNodeId(processTreeDTO.getNodeId());
        approvalDTO.setNodeNumber(processTreeDTO.getNodeNumber());
        approvalDTO.setApprovalDepart(processTreeDTO.getApprovalDepart());
        approvalDTO.setApprovalDepartName(processTreeDTO.getApprovalDepartName());
        if (StringUtils.hasLength(processTreeDTO.getTaskId())) {
            //进行中
            approvalDTO.setState(FlowConstants.FLOW_STATE_0);
//            approvalDTO.setAddTime(processTreeDTO.getCreateTime());
            if(SecurityUtils.getLoginUser().getCurrentOrgId().equals(processTreeDTO.getApprovalDepart())){
                approvalDTO.setApprovalName(SecurityUtils.getLoginUser().getUsername());
            }
            approvalDTO.setApprovalOrgName(orgService.idToDisplay(SecurityUtils.getLoginUser().getCurrentOrgId()));
        } else {
            if (StringUtils.hasLength(processTreeDTO.getApprovalName())) {
                //已结束
                approvalDTO.setState(FlowConstants.FLOW_STATE_1);
                approvalDTO.setAddTime(processTreeDTO.getAddTime());
                approvalDTO.setApprovalName(processTreeDTO.getApprovalName());
                approvalDTO.setApprovalOrgName(processTreeDTO.getApprovalOrgName());
            } else {
                //未开始
                approvalDTO.setState(FlowConstants.FLOW_STATE_2);
            }
        }
        approvalList.add(approvalDTO);
    }

    private void buildFlowCreate(FlowBill bill, FlowProcessTreeDTO flowProcessTreeDTO) {
        FlowProcessTreeDTO.FlowCreateDTO flowCreateDTO = new FlowProcessTreeDTO.FlowCreateDTO();
        flowCreateDTO.setBillState(bill.getBillState());
        flowCreateDTO.setProcessOrg(bill.getProcessOrg());
        flowCreateDTO.setProcessUserId(bill.getProcessUserId());
        flowCreateDTO.setProcessUserName(bill.getProcessUserName());
        flowCreateDTO.setProcessOrgName(bill.getProcessOrgName());
        flowCreateDTO.setInitiationTime(bill.getCreateTime());
        flowProcessTreeDTO.setFlowCreate(flowCreateDTO);
    }
}